package com.basker.pisces.orm.database;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.orm.data.meta.DataSetMeta;
import com.basker.pisces.orm.data.meta.FieldMeta;
import com.basker.pisces.orm.data.meta.IndexMeta;
import com.basker.pisces.orm.data.meta.TableMeta;
import com.basker.pisces.orm.parser.DBColumnInfo;
import com.basker.pisces.orm.parser.DBIndexInfo;
import com.basker.pisces.orm.parser.ISqlParser;
import com.basker.pisces.orm.session.ISqlSession;
import com.basker.pisces.orm.session.ISqlSessionFactory;
import com.basker.pisces.orm.session.SqlSessionFactoryManager;

/**
 * 负责数据库表结构的同步
 *
 * @author hangwen
 */
public class DatabaseDefinitionSynchronizer {

    private static final Logger logger = LoggerFactory.getLogger(DatabaseDefinitionSynchronizer.class);

    private DataSetMeta dataSetMeta;

    public DatabaseDefinitionSynchronizer(DataSetMeta dataSetMeta) {
        this.dataSetMeta = dataSetMeta;
    }

    public void syn() {
        try (ISqlSession session = this.getSqlSession()) {
            for (TableMeta tableMeta : this.dataSetMeta.getTableMetas()) {
                this.syn(tableMeta, session);

                List<TableMeta> subTableMetas = tableMeta.getSubTableMetas();
                for (TableMeta subTableMeta : subTableMetas) {
                    this.syn(subTableMeta, session);
                }
            }
        } catch (Exception e) {
            throw ExceptionFactory.createRuntimeException(e);
        }
    }

    private void addField(TableMeta tableMeta, FieldMeta fieldMeta, ISqlSession session, ISqlParser sqlParser) {
        String sql = sqlParser.getAddFieldSql(tableMeta, fieldMeta);

        try {
            session.execute(sql);

            if (logger.isDebugEnabled()) {
                logger.debug(MessageFormat.format("Alter Table ''{0}'' add Field ''{1}'' ,sql is:{2}",
                        tableMeta.getName(), fieldMeta.getName(), sql));
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error(
                        MessageFormat.format("Alter Table ''{0}'' add Field ''{1}'' occur exception:{3},sql is:{2}",
                                tableMeta.getName(), fieldMeta.getName(), sql, e.getMessage()));
            }

            throw e;
        }
    }

    private void addOrModifyField(TableMeta tableMeta, ISqlSession session, ISqlParser sqlParser,
                                  List<DBColumnInfo> columns) {
        Map<String, DBColumnInfo> columnMap = columns.stream()
                .collect(Collectors.toMap(col -> col.getName(), col -> col));

        Consumer<FieldMeta> synFieldMeta = fieldMeta -> {
            String fieldName = fieldMeta.getName();
            if (StringUtils.isEmpty(fieldName)) {
                return;
            }

            DBColumnInfo col = columnMap.get(fieldName);

            if (col == null) {
                // 创建新的字段
                this.addField(tableMeta, fieldMeta, session, sqlParser);
            } else if (this.isChanged(col, fieldMeta)) {
                // 目前仅支持长度和精度的修改，而且只允许增大，不可减小
                this.modifyFiled(tableMeta, fieldMeta, session, sqlParser);
            }
        };

        for (FieldMeta fieldMeta : tableMeta.getFieldMetas()) {
            synFieldMeta.accept(fieldMeta);
        }

        if (tableMeta.isChildTable()) {
            synFieldMeta.accept(tableMeta.getFkFieldMeta());
        }
    }

    private void createIndex(TableMeta tableMeta, IndexMeta indexMeta, ISqlSession session, ISqlParser sqlParser) {
        String sql = sqlParser.getCreateIndexSql(tableMeta, indexMeta);

        try {
            session.execute(sql);

            if (logger.isDebugEnabled()) {
                logger.debug(MessageFormat.format("Create Index ''{0}'' ON ''{1}'',sql is:{2}", indexMeta.getName(),
                        tableMeta.getName(), sql));
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error(MessageFormat.format("Create Index ''{0}'' ON ''{1}'' occur exception:{3},sql is:{2}",
                        indexMeta.getName(), tableMeta.getName(), sql, e.getMessage()));
            }

            throw e;
        }
    }

    private void createIndexes(TableMeta tableMeta, ISqlSession session) {
        List<IndexMeta> indexMetas = tableMeta.getIndexMetas();
        if (CollectionUtils.isEmpty(indexMetas)) {
            return;
        }

        ISqlParser sqlParser = session.getSqlParser();
        List<DBIndexInfo> tableIndexes = sqlParser.getTableIndexes(tableMeta, session);
        Map<String, DBIndexInfo> indexMap = tableIndexes.stream().collect(Collectors.toMap(i -> i.getName(), i -> i));

        for (IndexMeta indexMeta : indexMetas) {
            if (indexMap.containsKey(indexMeta.getName())) {
                continue;
            }

            this.createIndex(tableMeta, indexMeta, session, sqlParser);
        }

    }

    private void createTable(TableMeta tableMeta, ISqlSession session, ISqlParser parser) {
        List<FieldMeta> fieldMetas = tableMeta.getFieldMetas();
        if (fieldMetas.size() == 0) {
            return;
        }

        String sql = parser.getCreateTableSql(tableMeta);

        try {
            session.execute(sql);

            if (logger.isDebugEnabled()) {
                logger.debug(MessageFormat.format("Create table ''{0}'',sql is:{1}", tableMeta.getName(), sql));
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error(MessageFormat.format("Create table ''{0}'' occur exception:{2},sql is:{1}",
                        tableMeta.getName(), sql, e.getMessage()));
            }

            throw e;
        }

    }

    private ISqlSession getSqlSession() {
        ISqlSessionFactory factory = SqlSessionFactoryManager.getDfaultFactory();
        ISqlSession session = null;

        String schema = this.dataSetMeta.getSchema();
        if (StringUtils.isEmpty(schema)) {
            session = factory.createSession();
        } else {
            session = factory.createSession(schema);
        }

        return session;

    }

    private boolean isChanged(DBColumnInfo col, FieldMeta fieldMeta) {
        Integer length1 = col.getLength();
        Integer length2 = fieldMeta.getDataField().getLength();

        if (length1 != null && length2 != null) {
            if (length2 > length1) {
                // 长度增加 ，需要修改
                return true;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn(MessageFormat.format(
                            "Field:{0}''s previous length is {1},the new lenght {2} is not larger than it!",
                            fieldMeta.getName(), length1, length2));
                }
            }
        }

        Integer scale1 = col.getScale();
        Integer scale2 = fieldMeta.getDataField().getScale();

        if (scale1 != null && scale2 != null) {
            if (scale2 > scale1) {
                // 小数精度增加 ，需要修改
                return true;
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn(MessageFormat.format(
                            "Field:{0}''s previous scale is {1},the new scale {2} is not larger than it!",
                            fieldMeta.getName(), scale1, scale2));
                }
            }
        }

        String comment1 = col.getComment();
        String comment2 = fieldMeta.getComment();

        comment1 = comment1 != null ? comment1 : "";
        comment2 = comment2 != null ? comment2 : "";

        if (!comment1.equals(comment2)) {
            return true;
        }

        return false;
    }

    private void modifyFiled(TableMeta tableMeta, FieldMeta fieldMeta, ISqlSession session, ISqlParser sqlParser) {
        String sql = sqlParser.getModifyFieldSql(tableMeta, fieldMeta);

        try {
            session.execute(sql);

            if (logger.isDebugEnabled()) {
                logger.debug(MessageFormat.format("Modify field ''{0}'',sql is:{1}", fieldMeta.getName(), sql));
            }
        } catch (Exception e) {
            if (logger.isErrorEnabled()) {
                logger.error(MessageFormat.format("Modify field ''{0}'' occur exception:{2},sql is:{1}",
                        fieldMeta.getName(), sql, e.getMessage()));
            }

            throw e;
        }

    }

    private void syn(TableMeta tableMeta, ISqlSession session) {
        ISqlParser sqlParser = session.getSqlParser();
        List<DBColumnInfo> columns = sqlParser.getTableColumns(tableMeta, session);

        if (columns.size() == 0) {
            // 创建表
            this.createTable(tableMeta, session, sqlParser);
        } else {
            // 添加或修改字段
            this.addOrModifyField(tableMeta, session, sqlParser, columns);
        }

        // 添加索引
//        this.createIndexes(tableMeta, session);
    }

}
